﻿using System;
using System.Collections.Generic;
using msilgc.cbuilder.CSyntaxTree;
using msilgc.cbuilder.MSILBasicBlocksTranslator;
using msilgc.cbuilder.MSILSyntaxTree;
using msilgc.cbuilder.Reader;
using msilgc.common;
using msilgc.common.Attributes;
using msilgc.CSyntaxTree;
using System.Reflection;

namespace msilgc.cbuilder.CBuilder
{
    internal class CMethodBodyFactory {
        public CMethodBody Create(MethodInfo method) {
            var cMethodBody = new CMethodBody();

            cMethodBody.ReturnType = method.ReturnType;
            cMethodBody.MethodName = method.Name;
            cMethodBody.InitLocals = method.GetMethodBody().InitLocals;
            foreach (var localInfo in method.GetMethodBody().LocalVariables) {
                cMethodBody.LocalVariables.Add(new Variable("loc_" + localInfo.LocalIndex, localInfo.LocalType));
            }
            var i = (-1);
            foreach(var parameter in method.GetParameters()) {
                var argumentType = parameter.ParameterType;
                i += 1;
                if(argumentType == typeof(Grain) && parameter.GetCustomAttributes(typeof(ThreadIdxParameter), true).Length > 0) {
                    if (cMethodBody.ThreadIdxVariableOffset != (-1))
                    {
                        throw new Exception("Cannot specify ThreadGrainParameter attribute more than once");
                    }
                    cMethodBody.ThreadIdxVariableOffset = i;
                    cMethodBody.Arguments.Add(null);
                } else if (argumentType == typeof(Grain) && parameter.GetCustomAttributes(typeof(BlockIdxParameter), true).Length > 0) {
                    if(cMethodBody.BlockIdxVariableOffset != (-1))
                    {
                        throw new Exception("Cannot specify DataGrainParameter attribute more than once");
                    }
                    cMethodBody.BlockIdxVariableOffset = i;
                    cMethodBody.Arguments.Add(null);
                } else if(argumentType == typeof(Grain) && parameter.GetCustomAttributes(typeof(ThreadDimParameter), true).Length > 0) {
                    if(cMethodBody.ThreadDimVariableOffset != (-1))
                    {
                        throw new Exception("Cannot specify ThreadDimParameter attribute more than once");
                    }
                    cMethodBody.ThreadDimVariableOffset = i;
                    cMethodBody.Arguments.Add(null);
                } else if(argumentType == typeof(Grain) && parameter.GetCustomAttributes(typeof(BlockDimParameter), true).Length > 0) {
                    if (cMethodBody.BlockDimVariableOffset != (-1))
                    {
                        throw new Exception("Cannot specify BlockDimParameter attribute more than once");
                    }
                    cMethodBody.BlockDimVariableOffset = i;
                    cMethodBody.Arguments.Add(null);
                } else if(!argumentType.IsPrimitive && !argumentType.IsArray) {
                    throw new Exception("cannot pass arguments that are not .NET primitives");
                } else {
                    var arg = new Variable("arg_" + i, argumentType);
                    cMethodBody.Arguments.Add(arg);
                    arg.OutVariable = parameter.GetCustomAttributes(typeof (OutParameter), true).Length > 0;
                }
            }

            var instructions = MsilReader.ReadAll(method);
            var translator = new BasicBlocksTranslator();
            var varFactory = new VariableFactory();
            var instTranslator = new TranslatorFactory(varFactory);
            var msilBasicBlocks = translator.Translate(instructions, method.GetMethodBody());

            var blockHash = new Dictionary<BasicBlock, CBasicBlock>();

            var basicBlockNum = (-1);
            foreach (var msilBasicBlock in msilBasicBlocks) {
                var cBasicBlock = new CBasicBlock{
                                                             Label = "Label_" + (++basicBlockNum),
                                                             MsilBlock = msilBasicBlock
                                                         };
                blockHash[msilBasicBlock] = cBasicBlock;
                cMethodBody.BasicBlocks.Add(cBasicBlock);
            }
            foreach (var msilBasicBlock in msilBasicBlocks) {
                var cBasicBlock = blockHash[msilBasicBlock];
                foreach (var inst in msilBasicBlock.Instructions) {
                    var stmCount = cBasicBlock.Statements.Count;
                    instTranslator.CreateTranslator(inst).TranslateAndAdd(cMethodBody, cBasicBlock, msilBasicBlock, inst);
                    if(stmCount == cBasicBlock.Statements.Count) {
                        throw new Exception("factory for instruction: " + inst + " did not add to the C-Basic block");
                    }
                }
                if (msilBasicBlock.BranchInstruction != null) {
                    var stmCount = cBasicBlock.Statements.Count;
                    instTranslator.CreateTranslator(msilBasicBlock.BranchInstruction)
                        .TranslateAndAdd(cMethodBody, cBasicBlock, msilBasicBlock, msilBasicBlock.BranchInstruction);
                    if (stmCount == cBasicBlock.Statements.Count) {
                        throw new Exception("factory for instruction: " + msilBasicBlock.BranchInstruction + " did not add to the C-Basic block");
                    }
                }
                foreach (var cStatement in cBasicBlock.Statements) {
                    if(cStatement is ICStatementWithAssignment && ((ICStatementWithAssignment)cStatement).OnStack) {
                        throw new Exception("basic block evaluation stack is not empty!");
                    }
                }
            }
            cMethodBody.StackVariables.AddRange(varFactory.CreatedVariables);
            return cMethodBody;
        }
    }
}